﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using HAVNet.Core;
using HAVNet.Core.Array;
using HAVNet.Intros;
using HAVNet.Intros.AST;
using HAVNet.Transformers.Walkers;

namespace HAVNet.Transformers
{
    class Program
    {
        static void Main (string [] args)
        {
            string fname        = args [0];
            string [] ppsymbols = Arrays.skip (1, args);
            string result       = trabajar (fname, ppsymbols);
            
            Console.Out.WriteLine (result);
        }

        private static void usage ()
        {
            Console.Out.WriteLine ("\tAca va el texto de uso...");
        }

        static string trabajar (string nombreArchivo, string [] ppsymbols)
        {
            return transformarAST (crearAST (nombreArchivo, ppsymbols)).ToString();
        }

        private static ASTCS3 transformarAST (ASTCS3 ast)
        {
            Walkers.WalkerClasses.get (
                ast.namespaceMemberDecls,
                (@class, detph) => {
                    trabajarClase (@class);
                },
                0
            );

            return ast;
        }

        private static void trabajarClase (ASTTypeDeclClass @class)
        {
            //  Métodos
            //
            Walkers.WalkerClassesMethods.get (
                @class,
                (method) =>
                {
                    if (method.block == null)
                        return;

                    //  Si el método es "void" y no tiene ningun statement...
                    if ((method.returnType.typeKind == ASTTypeKind.Void) && (method.block.stmts.Count == 0))
                        method.block.stmts.Add(new ASTEmbStmtReturn());
                    
                    //  Si el método es "void" y el ultimo statement no es un return, agrego un return vacio: "return;"
                    //
                    if ((method.returnType.typeKind == ASTTypeKind.Void) && (method.block.stmts.Count > 0) && (!(method.block.stmts[method.block.stmts.Count - 1] is ASTEmbStmtReturn)))
                        method.block.stmts.Add(new ASTEmbStmtReturn());

                    //  Cambio los stmts que estan en la superficie...
                    //
                    for (int i = 0; i < method.block.stmts.Count; ++i)
                        if (method.block.stmts[i] is ASTEmbStmtReturn)
                            method.block.stmts[i] = transformarReturn(@class, method, method.block.stmts[i]);

                    //  Cambio los hijos de los superficiales...
                    //
                    for (int i = 0; i < method.block.stmts.Count; ++i)
                        WalkerStmts.get (
                            method.block.stmts[i],
                            (stmt, depth) =>
                            {
                                if (stmt is ASTEmbStmtBlock)
                                    for (int j = 0; j < ((ASTEmbStmtBlock)stmt).block.stmts.Count; ++j)
                                        if (((ASTEmbStmtBlock)stmt).block.stmts[j] is ASTEmbStmtReturn)
                                            ((ASTEmbStmtBlock)stmt).block.stmts[j] = transformarReturn(@class, method, ((ASTEmbStmtBlock)stmt).block.stmts[j]);

                                if (stmt is ASTEmbStmtChecked)
                                    for (int j = 0; j < ((ASTEmbStmtChecked)stmt).block.stmts.Count; ++j)
                                        if (((ASTEmbStmtChecked)stmt).block.stmts[j] is ASTEmbStmtReturn)
                                            ((ASTEmbStmtChecked)stmt).block.stmts[j] = transformarReturn(@class, method, ((ASTEmbStmtChecked)stmt).block.stmts[j]);

                                if ((stmt is ASTEmbStmtDo) && (((ASTEmbStmtDo)stmt).statement is ASTEmbStmtReturn))
                                    ((ASTEmbStmtDo)stmt).statement = transformarReturn(@class, method, ((ASTEmbStmtDo)stmt).statement);

                                if ((stmt is ASTEmbStmtFixed) && (((ASTEmbStmtFixed)stmt).statement is ASTEmbStmtReturn))
                                    ((ASTEmbStmtFixed)stmt).statement = transformarReturn(@class, method, ((ASTEmbStmtFixed)stmt).statement);

                                if ((stmt is ASTEmbStmtFor) && (((ASTEmbStmtFor)stmt).statement is ASTEmbStmtReturn))
                                    ((ASTEmbStmtFor)stmt).statement = transformarReturn(@class, method, ((ASTEmbStmtFor)stmt).statement);

                                if ((stmt is ASTEmbStmtForeach) && (((ASTEmbStmtForeach)stmt).statement is ASTEmbStmtReturn))
                                    ((ASTEmbStmtForeach)stmt).statement = transformarReturn(@class, method, ((ASTEmbStmtForeach)stmt).statement);

                                if (stmt is ASTEmbStmtIf)
                                {
                                    if (((ASTEmbStmtIf)stmt).thenStatement is ASTEmbStmtReturn)
                                        ((ASTEmbStmtIf)stmt).thenStatement = transformarReturn(@class, method, ((ASTEmbStmtIf)stmt).thenStatement);

                                    if (((ASTEmbStmtIf)stmt).elseStatement is ASTEmbStmtReturn)
                                        ((ASTEmbStmtIf)stmt).elseStatement = transformarReturn(@class, method, ((ASTEmbStmtIf)stmt).elseStatement);
                                }

                                if ((stmt is ASTEmbStmtLock) && (((ASTEmbStmtLock)stmt).statement is ASTEmbStmtReturn))
                                    ((ASTEmbStmtLock)stmt).statement = transformarReturn(@class, method, ((ASTEmbStmtLock)stmt).statement);

                                if (stmt is ASTEmbStmtSwitch)
                                    foreach (ASTSwitchSection switchSection in ((ASTEmbStmtSwitch)stmt).switchSections)
                                        for (int j = 0; j < switchSection.statements.Count; ++j)
                                            if (switchSection.statements[j] is ASTEmbStmtReturn)
                                                switchSection.statements[j] = transformarReturn(@class, method, switchSection.statements[j]);

                                if (stmt is ASTEmbStmtTry)
                                {
                                    for (int j = 0; j < ((ASTEmbStmtTry)stmt).tryBlock.stmts.Count; ++j)
                                        if (((ASTEmbStmtTry)stmt).tryBlock.stmts[j] is ASTEmbStmtReturn)
                                            ((ASTEmbStmtTry)stmt).tryBlock.stmts[j] = transformarReturn(@class, method, ((ASTEmbStmtTry)stmt).tryBlock.stmts[j]);

                                    if (null != ((ASTEmbStmtTry)stmt).catchs)
                                        for (int j = 0; j < ((ASTEmbStmtTry)stmt).catchs.Count; ++j)
                                            for (int k = 0; k < ((ASTEmbStmtTry)stmt).catchs[j].block.stmts.Count; ++k)
                                                if (((ASTEmbStmtTry)stmt).catchs[j].block.stmts[k] is ASTEmbStmtReturn)
                                                    ((ASTEmbStmtTry)stmt).catchs[j].block.stmts[k] = transformarReturn(@class, method, ((ASTEmbStmtTry)stmt).catchs[j].block.stmts[k]);

                                    //no se puede retornar de un finally
                                    //if (((ASTEmbStmtTry)stmt).finallyBlock != null)
                                    //    foreach (ASTStatement stmtX in ((ASTEmbStmtTry)stmt).finallyBlock.stmts)
                                    //        stmtWalk(stmtX, f, depth + 1);
                                }

                                if (stmt is ASTEmbStmtUnchecked)
                                    for (int j = 0; j < ((ASTEmbStmtUnchecked)stmt).block.stmts.Count; ++j)
                                        if (((ASTEmbStmtUnchecked)stmt).block.stmts[j] is ASTEmbStmtReturn)
                                            ((ASTEmbStmtUnchecked)stmt).block.stmts[j] = transformarReturn (@class, method, ((ASTEmbStmtUnchecked)stmt).block.stmts[j]);

                                if (stmt is ASTEmbStmtUnsafe)
                                    for (int j = 0; j < ((ASTEmbStmtUnsafe)stmt).block.stmts.Count; ++j)
                                        if (((ASTEmbStmtUnsafe)stmt).block.stmts[j] is ASTEmbStmtReturn)
                                            ((ASTEmbStmtUnsafe)stmt).block.stmts[j] = transformarReturn (@class, method, ((ASTEmbStmtUnsafe)stmt).block.stmts[j]);

                                if (stmt is ASTEmbStmtUsing)
                                    if (((ASTEmbStmtUsing)stmt).statement is ASTEmbStmtReturn)
                                        ((ASTEmbStmtUsing)stmt).statement = transformarReturn (@class, method, ((ASTEmbStmtUsing)stmt).statement);

                                if ((stmt is ASTEmbStmtWhile) && (((ASTEmbStmtWhile)stmt).statement is ASTEmbStmtReturn))
                                    ((ASTEmbStmtWhile)stmt).statement = transformarReturn (@class, method, ((ASTEmbStmtWhile)stmt).statement);
                            },
                            0);

                    //  Inserto la llamada Trace.enterMethod en el bloque, como primer statement.
                    //
                    method.block.stmts.Insert (0, crearLlamadoEnterMethod(method));
                }
            );
        }

        private static ASTCS3 crearAST (string nombreArchivo, string [] ppsymbols)
        {
            return new ASTBuilder().build(nombreArchivo, ppsymbols);
        }

        static ASTStatement crearLlamadoEnterMethod (ASTMemberDeclMethod method)
        {
            ASTBuilder  ptb     = new ASTBuilder();
            ASTCS3      cs3     = null;
            string      sCodigo = @"
                namespace Foo
                {
                    class ClassFoo
                    {
                        void methodFoo ()
                        {
                            HAVDebug.Trace.enterMethod (
                                System.Threading.Thread.CurrentThread.ManagedThreadId,
                                System.Threading.Thread.CurrentThread.Name,
                                ""<PARAM0>"",
                                ""<PARAM1>"",
                                  <PARAM3>
                            );
                        }
                    }
                }
            ";

            sCodigo = sCodigo.Replace("<PARAM0>", method.name.ToString());
            sCodigo = sCodigo.Replace("<PARAM1>", method.returnType.ToString());
            sCodigo = sCodigo.Replace("<PARAM3>", dameValoresParametros (method.formalParamList));

            // crea el AST del statement...
            using (Stream s = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(sCodigo)))
                cs3 = ptb.build (s, null);

            ASTStatement stmtAux = null;

            WalkerClasses.get(
                cs3.namespaceMemberDecls,
                new ClassFound(
                    delegate(ASTTypeDeclClass @class, int depthx) {
                        WalkerClassesMethods.get (
                            @class,
                            new ClassMethodFound (
                                delegate (ASTMemberDeclMethod methodX)
                                {
                                    if (methodX.block != null)
                                        if (methodX.block.stmts.Count > 0)
                                            stmtAux = methodX.block.stmts[0];
                                }));
                    }
                ),
            0);

            return stmtAux;
        }

        private static ASTStatement transformarReturn (ASTTypeDeclClass @class, ASTMemberDeclMethod method, ASTStatement @return)
        {
            ASTBuilder  ptb     = new ASTBuilder();
            ASTCS3      cs3     = null;
            string      sCodigo = "";

            if (method.returnType.typeKind == ASTTypeKind.Void)
            {
                sCodigo = @"
                    namespace Foo
                    {
                        class ClassFoo
                        {
                            void methodFoo ()
                            {
                                /* este es el embedded-statement-block que quiero robar... */
                                HAVDebug.Trace.exitMethod (
                                    System.Threading.Thread.CurrentThread.ManagedThreadId,
                                    System.Threading.Thread.CurrentThread.Name,
                                    ""<METHOD_NAME>""
                                );
                            }
                        }
                    }
                ";
            }
            else
            {
                if (((ASTEmbStmtReturn)@return).expression.ToString().StartsWith("_____"))
                    return @return;

                sCodigo = @"
                    namespace Foo
                    {
                        class ClassFoo
                        {
                            /* aca reemplazo el tipo casi sin ningun motivo, solo conservo la coherencia, por mas que no se necesite.. */
                            <TIPO_RET> methodFoo ()
                            {
                                /* este es el embedded-statement-block que quiero robar... */
                                {
                                    <TIPO_RET> _____aux = <EXPRESION> ;
                                    HAVDebug.Trace.exitMethod<<TIPO_RET>> (System.Threading.Thread.CurrentThread.ManagedThreadId, System.Threading.Thread.CurrentThread.Name, ""<METHOD_NAME>"", _____aux);
                                    return _____aux ;
                                }
                            }
                        }
                    }
                ";

                sCodigo = sCodigo.Replace("<TIPO_RET>", method.returnType.ToString());
                sCodigo = sCodigo.Replace("<EXPRESION>", ((ASTEmbStmtReturn)@return).expression.ToString());
            }

            sCodigo = sCodigo.Replace("<METHOD_NAME>", method.name.ToString());

            //  crea el AST del statement...
            using (Stream s = new MemoryStream (Encoding.UTF8.GetBytes(sCodigo)))
                cs3 = ptb.build (s, null);

            ASTStatement stmtAux = null;
            
            WalkerClasses.get (
                cs3.namespaceMemberDecls,
                (xclass, depthx) =>
                {
                    WalkerClassesMethods.get(
                        xclass,
                        new ClassMethodFound(
                            delegate(ASTMemberDeclMethod methodX)
                            {
                                if (methodX.block != null)
                                    if (methodX.block.stmts.Count > 0)
                                        stmtAux = methodX.block.stmts[0];
                            }));
                }, 0);

            return stmtAux;
        }

        static string dameValoresParametros (ASTFormalParamList fpl)
        {
            string sRet     = @"string.Format (   ";
            string sFormat  = @"""";

            for (int i = 0; i < fpl.Count; ++i)
                sFormat += fpl[i].ident + "={" + i + "} ";

            sFormat += @"""";
            sRet    += sFormat;

            foreach (ASTFormalParam fp in fpl)
                sRet += ", HAVDebug.Trace.paramProcessor(" + fp.ident + ")";

            return sRet + ")";
        }
    }
}
